package model;

import java.awt.Color;
import java.awt.Point;
import java.io.IOException;
import java.util.*;

import com.nightpiter.controller.log.Logger;
import com.nightpiter.model.MetroModelData;
import com.nightpiter.model.exceptions.BuilderException;
import com.nightpiter.model.exceptions.ModelNotAssignedBuilderException;
import com.nightpiter.model.objects.*;

import exceptions.ReaderNotFoundException;

import model.objects.EdiLine;
import model.objects.EdiStation;
import model.objects.EdiTransfer;
import model.objects.EdiTunnel;
import model.readers.MetroModelReaderFactory;
import model.savers.MetroSaversFactory;
import model.savers.MetroSaversRegistry;
import model.savers.StandardSaver;



public class MetroModel implements Iterable<EdiStation>{

	private String cityName = "";
	
	private String comment = "";

	private ArrayList<String> authors = new ArrayList<String>();

	private List<EdiStation> stations = new ArrayList<EdiStation>();

	private ArrayList<Hub> hubs = new ArrayList<Hub>();

	private ArrayList<EdiLine> lines = new ArrayList<EdiLine>();

	private List<EdiTransfer> transfers = new ArrayList<EdiTransfer>();

	private List<EdiTunnel> tunnels = new ArrayList<EdiTunnel>();

	private static MetroModel model = new MetroModel();
	
	private List<ModelChangeListener> listeners = new LinkedList<ModelChangeListener>();
	
	/**
	 * actions list for undo
	 */
	//TODO set to circled list 
	private LinkedList<Action> undoes = new LinkedList<Action>();
	private LinkedList<Action> redoes = new LinkedList<Action>();
	/**
	 * this tree allow to quickly find station by it's name
	 */
	TreeMap<String, Station> stationsByNameIndex;

		

	private MetroModel() {
		
	}
	
	public static MetroModel getInstance() {
		return model;
	}
	

	/**
	 * * build method
	 * 
	 * package protected (should be called from MetroModelBuilder's build
	 * method)
	 * 
	 * @param stations
	 *            the underground stations
	 * @param transfers
	 *            the underground transfers
	 * @param tunnels
	 *            the underground tunnels
	 * @param hubs
	 *            the underground hubs ("linked" transfers)
	 * @param lines
	 *            the underground lines ("linked" tunnels)
	 */
	
	void build(model.EditorMetroModelData data) {
		this.authors=data.getAuthors();
		this.cityName = data.getCityName();
		this.comment = data.getComment();
		this.lines = data.getEdiLines();
		this.hubs = data.getHubs();
		this.stations = data.getEdiStations();
		this.transfers = data.getEdiTransfers();
		this.tunnels = data.getEdiTunnels();
		
	}
	
	/**
	 * cast List<Station> to List<EdiStation>
	 * @param stations
	 * @return
	 *//*
	private List<EdiStation> castStationsList(List<Station> stations) {
		List<EdiStation> st = new LinkedList<EdiStation>(); 
		for(Station s: stations) {
			st.add(new EdiStation(s));
		}
		return st;
	}*/
	
		
	
	

	/**
	 * loads a new model data
	 * 
	 * @param filePath
	 *            the file path; could be null - in this case the metro data
	 *            would be loaded from resources
	 */
	public void load(String filePath) {
		try {
			MetroModelReaderFactory.getReader(filePath).read(filePath, MetroModel.getInstance());			
		} catch (ReaderNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.modelChanged(true);
		
		
	}
	
	public void save(String filePath) {
		try {
			MetroSaversFactory.getSaver(filePath).save(filePath, getInstance());
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	
	
	
	/**
	 * @return the authors
	 */
	public List<String> getAuthors() {
		return authors;
	}

	/**
	 * @param authors
	 *            the authors to set
	 */
	void setAuthors(ArrayList<String> authors) {
		this.authors = authors;
	}

	/**
	 * @return the comment
	 */
	public String getComment() {
		return comment;
	}

	/**
	 * @param comment
	 *            the comment to set
	 */
	void setComment(String comment) {
		this.comment = comment;
	}

	/**
	 * @param cityName
	 *            the name of the city represented in this Model
	 */
	void setCityName(String cityName) {
		this.cityName = cityName;
	}

	/**
	 * @return the cityName
	 */
	public String getCityName() {
		return cityName;
	}

	/**
	 * @return the stations
	 */
	public List<EdiStation> getStations() {
		return stations;
	}

	/**
	 * @return the transfers
	 */
	public List<EdiTransfer> getTransfers() {
		return transfers;
	}

	/**
	 * @return the tunnels
	 */
	public List<EdiTunnel> getTunnels() {
		return tunnels;
	}

	/**
	 * @return the hubs
	 */
	public List<Hub> getHubs() {
		return hubs;
	}

	/**
	 * @return the lines
	 */
	public List<EdiLine> getLines() {
		return lines;
	}
	
	/**
	 * perform new action
	 * @param action
	 */
	public void performAction(Action action) {
		redoes.clear();
		action.perform();
		
		if(undoes.isEmpty()) {
			undoes.add(action);
		}
		else if(!undoes.getLast().merge(action)) {
			undoes.add(action);
		}
		this.modelChanged(action.isAddindObjects());
	}
	
	/**
	 * undo last actions
	 */
	public void undo() {		
		Action a = undoes.getLast(); 
		a.undo();
		undoes.removeLast();
		redoes.add(a);
		this.modelChanged(a.isAddindObjects());
	}
	
	public void redo() {
		Action a = redoes.getLast(); 
		a.perform();
		redoes.removeLast();
		undoes.add(a);
		this.modelChanged(a.isAddindObjects());
		
	}
	
	public MetroModelData getModelData() {
		EditorMetroModelData data = new EditorMetroModelData();
		data.init(stations, transfers, tunnels, hubs, lines, cityName, authors, comment);
		return data;
	}
	public Station addStation(String name, Coordinates position, Coordinates location, Time openTime, Time closeTime) {
		EdiStation station = new EdiStation(name,position, location, openTime,closeTime,0,0,0,true);
		performAction(new AddStationAction(station));
		return station;
	}
	
	public void removeStations(List<EdiStation> stations) {
		performAction(new RemoveStationsAction(stations));		
	}
	
	public void remove(List<EdiStation> stations,List<EdiTunnel> tunnels) {
		performAction(new RemoveAction(stations,tunnels));		
	}
	
	public void removeTunnel(List<EdiTunnel> tunnels) {		
		performAction(new RemoveTunnelAction(tunnels));		
	}
	
	public Transfer addTransfer(Station from, Station to, int time) {
		Transfer transfer = new EdiTransfer(from,to,time);
		performAction(new AddTransitionAction(transfer));
		return transfer;
	}
	
	public Tunnel addTunnel(Station from, Station to, Line line, int time, boolean smooth) {
		Tunnel tunnel = new EdiTunnel(from,to,time,line, smooth);
		performAction(new AddTransitionAction(tunnel));
		
		return tunnel;
	}
	
	public void moveStations(List<EdiStation> stations, float dx, float dy) {
		performAction(new MoveStationsAction(stations,dx,dy));		
	}
	
	public void moveStation(EdiStation station, Coordinates position) {
		performAction(new MoveStationAction(station,position));		
	}
	
	public void moveStationLabel(EdiStation station, int dx, int dy) {
		performAction(new MoveStationLabelAction(station,dx,dy));		
	}
	
	public void setStationName(EdiStation station, String name) {
		performAction(new StationNameSetAction(station,name));
	}
	
	public void SetStationLocation(EdiStation station, Coordinates point) {
		performAction(new StationLocationChangeAction(station,point));		
	}
	
	public void setStationTime(EdiStation station, Time open, Time close) {
		performAction(new StationTimeChangeAction(station, open,close));
	}
	

	public void setTunnelTime(EdiTunnel tunnel, int time) {
		performAction(new TunnelTimeChangeAction(tunnel, time));
		
	}
	

	/**
	 * added for comfortable iteration between station
	 * @return stations iterator
	 */
	public Iterator<EdiStation> iterator() {
		return stations.iterator();
	}
	
	public void addModelChangeListener(ModelChangeListener listener) {
		this.listeners.add(listener);
	}
	public void modelChanged(boolean newObjectsAdded) {
		for(ModelChangeListener l:listeners) {
			l.modelChanged(newObjectsAdded);
		}
	}
	
	
	
	/**
	 * action classes
	 * @author ilya shurigyn
	 *
	 */
	class AddStationAction implements Action {

		private EdiStation station; 
		
		public AddStationAction(EdiStation station) {
			this.station = station;
		}
		
		
		public boolean merge(Action action) {		
			return false;
		}

		
		public void perform() {		
			//ToDo throw exception if station doubling
			stations.add(station);
		}

		
		public void undo() {			
			stations.remove(station);			
		}


		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return true;
		}
		
	}
	
	
	class RemoveStationsAction implements Action {
		List<RemoveStationAction> actions = new LinkedList<RemoveStationAction>();
		public RemoveStationsAction(List<EdiStation> stations) {
			for(EdiStation station:stations) {
				actions.add(new RemoveStationAction(station));
			}
		}
		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return true;
		}
		@Override
		public boolean merge(Action action) {
			// TODO Auto-generated method stub
			return false;
		}
		@Override
		public void perform() {
			for(RemoveStationAction action:actions)
				action.perform();
			
		}
		@Override
		public void undo() {
			for(RemoveStationAction action:actions)
				action.undo();
			
		}
	}
	
	class RemoveAction implements Action {
		RemoveStationsAction a1;
		RemoveTunnelAction a2;
		public RemoveAction(List<EdiStation> stations, List<EdiTunnel> tunnels) {
			a1 = new RemoveStationsAction(stations);
			a2 = new RemoveTunnelAction(tunnels);
		}
		@Override
		public boolean isAddindObjects() {
			
			return true;
		}
		@Override
		public boolean merge(Action action) {
			
			return false;
		}
		@Override
		public void perform() {
			a2.perform();
			a1.perform();
			
			
		}
		@Override
		public void undo() {
			a2.undo();
			a1.undo();
			
		}
		
	}
	class RemoveStationAction implements Action {
		private EdiStation station;
		
		/**
		 * lists of transitions to restore on undo
		 */
		private List<Tunnel> tunnels;
		private List<Transfer> transfers;
		
		public RemoveStationAction(EdiStation station) {
			this.station = station;
		}
		
		public boolean merge(Action action) {			
			return false;
		}

		
		public void perform() {			
			//adding outgoing transitions to transitions list 
			tunnels = new LinkedList<Tunnel>();
			tunnels.addAll(station.getTunnels());
			transfers = new LinkedList<Transfer>(); 
			transfers.addAll(station.getTransfers());
			
			//add all incoming transitions to transitions list 
			for(Station s:model) {				
				for(Tunnel t:s.getTunnels()) {
					if(t.getToStation()==station) {
						tunnels.add(t);
						
					}
				}
				
				for(Transfer t:s.getTransfers()) {
					if(t.getToStation()==station) {
						transfers.add(t);
						
					}
				}				
				
			}			
			
			for(Tunnel tunnel:tunnels) {
				
				//tunnels.add(tunnel);
				MetroModel.getInstance().tunnels.remove(tunnel);
			}
			for(Transfer transfer:transfers) {
				//transfers.add(transfer);
				MetroModel.getInstance().transfers.remove(transfer);
			}
			
			stations.remove(station);
		}

		
		public void undo() {
			for(Transfer t:this.transfers) {
				MetroModel.getInstance().transfers.add((EdiTransfer) t);
				t.getFromStation().getTransfers().add(t);				
			}
			for(Tunnel t:this.tunnels) {
				MetroModel.getInstance().tunnels.add((EdiTunnel) t);
				t.getFromStation().getTunnels().add(t);				
			}
			MetroModel.getInstance().stations.add(station);
		}

		@Override
		public boolean isAddindObjects() {
			
			return true;
		}
		
	}
	
	class RemoveTunnelAction implements Action {
				
		/**
		 * lists of transitions to restore on undo
		 */
		private List<EdiTunnel> tunnels;
		//private List<Transfer> transfers;
		
		public RemoveTunnelAction(List<EdiTunnel> tunnels) {
			this.tunnels = tunnels;
		}
		
		public boolean merge(Action action) {			
			return false;
		}

		
		public void perform() {		
			for(EdiTunnel tunnel:tunnels) {
				tunnel.getFromStation().getTunnels().remove(tunnel);
				MetroModel.getInstance().tunnels.remove(tunnel);
			}
			
		}

		
		public void undo() {
			for(EdiTunnel tunnel:tunnels) {
				tunnel.getFromStation().getTunnels().add(tunnel);
				MetroModel.getInstance().tunnels.add(tunnel);
			}
		}

		@Override
		public boolean isAddindObjects() {
			
			return true;
		}
		
	}
	
	class AddTransitionAction implements Action {
		Transition transition;
		
		public AddTransitionAction(Transition transition) {
			this.transition = transition;
		}
		
		
		public boolean merge(Action action) {			
			return false;
		}

		
		public void perform() {
			
			if(Tunnel.class.isInstance(transition)) {
				transition.getFromStation().getTunnels().add((Tunnel)transition);
				tunnels.add((EdiTunnel)transition);
				
			} else if(Transfer.class.isInstance(transition)) {
				transition.getFromStation().getTransfers().add((Transfer)transition);
				transfers.add((EdiTransfer)transition);
			}
		}

		public void undo() {
			if(Tunnel.class.isInstance(transition)) {
				transition.getFromStation().getTunnels().remove((Tunnel)transition);
				tunnels.remove((EdiTunnel)transition);
			} else if(Transfer.class.isInstance(transition)) {
				transition.getFromStation().getTransfers().remove((Transfer)transition);
				transfers.remove((EdiTransfer)transition);
			}
		}


		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return true;
		}
		
	}
	class RemoveTransitionAction implements Action {
		Transition transition;
		public RemoveTransitionAction(Transition transition) {
			this.transition = transition;
		}
		
		public boolean merge(Action action) {			
			return false;
		}

		
		public void undo() {
			if(transition.getClass().equals(Tunnel.class)) {
				transition.getFromStation().getTunnels().remove((Tunnel)transition);
			} else if(transition.getClass().equals(Transfer.class)) {
				transition.getFromStation().getTransfers().remove((Transfer)transition);
			}
		}

		
		public void perform() {
			if(transition.getClass().equals(Tunnel.class)) {
				((EdiStation)(transition.getFromStation())).addTunnel((Tunnel)transition);
			} else if(transition.getClass().equals(Transfer.class)) {
				((EdiStation)(transition.getFromStation())).addTransfer((Transfer)transition);
			}
		}

		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return true;
		}
		
	}
	
	class MoveStationsAction implements Action {

		double dx,dy;
		List<EdiStation> stations;
		List<Action> actions = new LinkedList<Action>();
		
		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}

		
		public MoveStationsAction(List<EdiStation> stations, double dx, double dy) {
			this.stations = stations;
			this.dx=dx;
			this.dy=dy;
		}

		@Override
		public boolean merge(Action action) {
			if(MoveStationsAction.class.isInstance(action)) {
				MoveStationsAction t = (MoveStationsAction)action;
				if(isListEquals(stations, t.stations)) {
					this.undo();
					dx+=t.dx;
					dy+=t.dy;
					this.perform();
					return true;
				}
				
			}
			return false;
		}

		@Override
		public void perform() {
			for(EdiStation station: stations) {
				Coordinates newPosition = new Coordinates(station.getPosition().x+(int)dx,station.getPosition().y+(int)dy);
				MoveStationAction moveStationAction = new MoveStationAction(station, newPosition);
				actions.add(moveStationAction);
				moveStationAction.perform();
			}
			
			
		}

		@Override
		public void undo() {
			for(Action action:actions) {
				action.undo();
			}
			
		}
		
		boolean isListEquals(List list1, List list2) {
			int i=0;
			for(Object o: list1) {
				if(!list2.contains(o)) return false;
				i++;
			}
			if(list2.size()==i) return true;
			return false;
		}
		
	}
	class MoveStationAction implements Action {

		private Coordinates oldPosition, newPosition; 
		private EdiStation station;
		
		public MoveStationAction(EdiStation station, Coordinates position) {
			this.newPosition = position;
			this.oldPosition = station.getPosition();
			this.station = station;
		}
		
		@Override		
		public boolean merge(Action action) {
			return false;
		}

		@Override
		public void perform() {
			station.setPosition(newPosition);
		}

		@Override
		public void undo() {
			station.setPosition(oldPosition);
			
		}

		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}
		
		
		
	}
	
	class StationLocationChangeAction implements Action {

		private Coordinates oldPosition, newPosition; 
		private EdiStation station;
		
		public StationLocationChangeAction(EdiStation station, Coordinates location) {
			this.newPosition = location;
			this.oldPosition = station.getLocation();
			this.station = station;
		}
		
		@Override		
		public boolean merge(Action action) {			
			return false;
		}

		@Override
		public void perform() {
			
			station.setLocation(newPosition);
		}

		@Override
		public void undo() {
			station.setLocation(oldPosition);
			
		}

		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}
		
	}
	
	class MoveStationLabelAction implements Action {

		private Point oldPosition, newPosition; 
		private EdiStation station;
		
		public MoveStationLabelAction(EdiStation station, int dx, int dy) {
			this.newPosition = new Point(dx,dy);
			this.oldPosition = new Point(station.getLabelX(), station.getLabelY());
			this.station = station;
		}
		
		@Override		
		public boolean merge(Action action) {			
			if(MoveStationLabelAction.class.isInstance(action)) {
				MoveStationLabelAction t = (MoveStationLabelAction)action;
				if(t.getStation()==station) {
					
					newPosition = new Point(this.getDx()+t.getDx(),this.getDy()+t.getDy());

					return true;
				}
			}
			return false;
		}

		@Override
		public void perform() {
			
			station.setTextRelativePosition(newPosition.x,newPosition.y);
		}

		@Override
		public void undo() {
			station.setTextRelativePosition(oldPosition.x,oldPosition.y);
			
		}

		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}
		
		int getDx() {return newPosition.x;}
		int getDy() {return newPosition.y;}
		EdiStation getStation() {return station;}
		
	}
	
	class StationNameSetAction implements Action {

		private String oldName;
		private String newName;
		private EdiStation station;
		
		public StationNameSetAction(EdiStation station, String name) {
			this.station = station;
			oldName = station.getName();
			newName = name;
		}
		@Override
		public boolean merge(Action action) {			
			return false;
		}

		@Override
		public void perform() {			
			station.setName(newName);
		}

		@Override
		public void undo() {
			station.setName(oldName);
			
		}
		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}
		
	}
	
	class StationTimeChangeAction implements Action {
		private EdiStation station;
		private Time oldOpenTime, oldCloseTime;
		private Time newOpenTime, newCloseTime;
		
		public  StationTimeChangeAction(EdiStation station, Time open, Time close) {
			this.station = station;
			this.newCloseTime = close;
			this.newOpenTime = open;
			this.oldCloseTime = station.getOpenTime();
			this.oldOpenTime = station.getCloseTime();			
		}
		
		@Override
		public boolean merge(Action action) {			
			return false;
		}

		@Override
		public void perform() {
			station.setOpenTime(newOpenTime);
			station.setCloseTime(newCloseTime);
		}

		@Override
		public void undo() {
			station.setOpenTime(oldOpenTime);
			station.setCloseTime(oldCloseTime);
			
		}

		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}
		
	}
	
	class TunnelTimeChangeAction implements Action {

		private EdiTunnel tunnel;
		private int oldTime;
		private int newTime;
		
		public TunnelTimeChangeAction(EdiTunnel tunnel, int time) {
			
			this.tunnel=tunnel;
			newTime=time;
			oldTime=tunnel.getTime();
			
		}

		@Override
		public boolean isAddindObjects() {		
			return false;
		}

		@Override
		public boolean merge(Action action) {		
			return false;
		}

		@Override
		public void perform() {
			Logger.log(oldTime + ":" + newTime);
			tunnel.setTime(newTime);

		}

		@Override
		public void undo() {
			Logger.log(oldTime + ":" + newTime);
			tunnel.setTime(oldTime);

		}

	}
	class HardSclaeAction implements Action {
		private float scale;
		@Override
		public boolean isAddindObjects() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean merge(Action arg0) {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public void perform() {
			for(EdiStation station:stations) {
				int newX = (int) (station.getPosition().x*scale);
				int newY = (int) (station.getPosition().y*scale);
				Coordinates newPosition = new Coordinates(newX,newY);
				station.setPosition(newPosition);
			}
			
		}

		@Override
		public void undo() {
			for(EdiStation station:stations) {
				int newX = (int) (station.getPosition().x/scale);
				int newY = (int) (station.getPosition().y/scale);
				Coordinates newPosition = new Coordinates(newX,newY);
				station.setPosition(newPosition);
			}
			
		}
		
	}
	public void clear() {
		authors = new ArrayList<String>();

		stations = new ArrayList<EdiStation>();

		hubs = new ArrayList<Hub>();

		lines = new ArrayList<EdiLine>();

		transfers = new ArrayList<EdiTransfer>();

		tunnels = new ArrayList<EdiTunnel>();
		
		modelChanged(true);
		
	}
	class LineNameChangeAction implements Action {

		private EdiLine line; 
		private String oldName, newName;
		public LineNameChangeAction(EdiLine line, String newName) {
			this.line = line;
			this.newName = newName;
			this.oldName = line.getName();
		}
		
		@Override
		public boolean isAddindObjects() {
			return false;
		}

		@Override
		public boolean merge(Action action) {
			return false;
		}

		@Override
		public void perform() {
			line.setName(newName);
			
		}

		@Override
		public void undo() {
			line.setName(oldName);
			
		}
		
	}
	
	class LineDelayChangeAction implements Action {

		private EdiLine line; 
		private int oldDelay, newDelay;
		public LineDelayChangeAction(EdiLine line, int delay) {
			this.line = line;
			this.oldDelay = line.getTrainDelay();
			this.newDelay = delay;
		}
		
		@Override
		public boolean isAddindObjects() {
			return false;
		}

		@Override
		public boolean merge(Action action) {
			return false;
		}

		@Override
		public void perform() {
			line.setDelay(newDelay);
			
		}

		@Override
		public void undo() {
			line.setDelay(oldDelay);
			
		}
		
	}
	
	class LineColorChangeAction implements Action {

		private EdiLine line; 
		private int oldColor, newColor;
		public LineColorChangeAction(EdiLine line, Color color) {
			this.line = line;
			this.oldColor = line.getColor();
			this.newColor = color.getRGB();
		}
		
		@Override
		public boolean isAddindObjects() {
			return false;
		}

		@Override
		public boolean merge(Action action) {
			return false;
		}

		@Override
		public void perform() {
			line.setColor(newColor);
			
		}

		@Override
		public void undo() {
			line.setColor(oldColor);
			
		}
		
	}
	
	class AddLineAction implements Action {

		private EdiLine line; 
		
		public AddLineAction(EdiLine line) {
			this.line = line;			
		}
		
		@Override
		public boolean isAddindObjects() {
			return false;
		}

		@Override
		public boolean merge(Action action) {
			return false;
		}

		@Override
		public void perform() {
			lines.add(line);
			
		}

		@Override
		public void undo() {
			lines.remove(line);
			
		}
		
	}
	
	public void setLineName(EdiLine line, String name) {
		performAction(new LineNameChangeAction(line, name));
		
	}

	public void addLine(EdiLine line) {		
		performAction(new AddLineAction(line));
	}

	public void setLineDelay(EdiLine line, int delay) {
		performAction(new LineDelayChangeAction(line,delay));
		
	}

	public void setLineColor(EdiLine line, Color color) {
		performAction(new LineColorChangeAction(line,color));
		
	}

	
}

interface Action {
	
	public void perform();
	public boolean isAddindObjects();
	public void undo();
	
	/**
	 * try to merge two actions(Station moving, for example)
	 * @param action
	 * @return true if merging is successful, otherwise false 
	 */
	public boolean merge(Action action);	 
}

